Self-Learned Algorithms - 03
This is my learning note about algorithms.
Reference
- 《小浩算法》) - 动态规划系列
70.爬楼梯
https://leetcode-cn.com/problems/climbing-stairs/
题解
- 很简单的dp题,不过在评论区找到很多有意思的解法
解法
- 递归法:容易超时,python可以加个缓存装饰器
1 |
|
- DP1:新建一个字典或者数组来存储以前的变量,空间复杂度O(n)
1 | dp = {} |
- DP2:只存储前两个元素,减少了空间,空间复杂度O(1)
1 | if n < 3: return n |
- 斐波那契数列
1 | import math |
- 面向测试用例编程(笑死)
1 | a = [1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368,75025,121393,196418,317811,514229,832040,1346269,2178309,3524578,5702887,9227465,14930352,24157817,39088169,63245986,102334155,165580141,267914296,433494437,701408733,1134903170,1836311903] |
- 利用排列组合
C(n,m)=n!/m!(n-m)!
,可参考该解法
1 | def fac(self, num): |
53.最大子序和
https://leetcode-cn.com/problems/maximum-subarray/
题解
- 设定状态
dp[i]
为以nums[i]
为结尾的连续子数组最大和 - 结果为
i
的情况下nums[i]
必定为正数 - 如果
dp[i-1]
为负数,那么dp[i]=nums[i]
解法
- 一边遍历一边计算:
nums[i-1]
并不是数组前一项的意思,而是到前一项为止的最大子序和,和0比较是因为只要大于0,就可以相加构造最大子序和。
1 | for i in range(1, len(nums)): |
- 正经dp
1 | dp = nums[:] |
简化版:只用一个值保存结果
1 | pre = nums[0] |
300.最长上升子序列
https://leetcode-cn.com/problems/longest-increasing-subsequence/
题解
- LIS可能是连续的,也可能是非连续的(不严格审题以为$O(N)$能解决的)
- “上升”是“严格上升”,类似于
[2, 3, 3, 6, 7]
这样的子序列是不符合要求的
解法
- 动态规划:
dp[i]
的值代表以nums[i]
为结尾的最长子序列长度,每轮计算新dp[i]
时,遍历[0,i)
列表区间。时间复杂度$O(N^2)$。
1 | if not nums: return 0 |
- 二分查找:是否可以通过重新设计状态定义,使整个
dp
为一个排序列表;这样在计算每个dp[k]
时,就可以通过二分法遍历[0,k)
区间元素,将此部分复杂度由$O(N)$降至$O(logN)$。
1 | tails, res = [0] * len(nums), 0 |
二分查找的运行时间居然比普通动态规划的快20倍,惊了!
120.三角形最小路径和
https://leetcode-cn.com/problems/triangle/
题解
- 常规动态规划题目,需要考虑一下边界的问题。从上到下和从下到上都可以做。
解法
- 运气解法(又名reinforcement learning)(笑死):随机的瞎走,走上个10000遍,只保留步数最小的那一个
1 | sum = 9999999 |
- 原地修改自底而上
1 | for i in range(len(triangle) - 1, 0, -1): |
- 自上而下:状态转移方程中的
dp[i-1][j-1]
中的j-1
,当j=0
时,其效果等同于dp[i-1][-1]
,即无穷大值float('inf')
;同理,j=i
时dp[i-1][j]
也表示无穷大值,因此不需要再进行边界判别。
1 | dp = [[float('inf')] * len(triangle) for _ in range(len(triangle))] |
64.最小路径和
https://leetcode-cn.com/problems/minimum-path-sum/
题解
- 和上一道题差不多,状态转移也只有两个。
- (思考题)路径和类问题和之前的子序列类问题有何区别?
解法
- 在原数组上进行记忆
1 | for i in range(len(grid)): |
- 反向动态规划:边界条件与正向相似
1 | x = len(grid[0]) |
1 | dct = {} |
1 | dct = defaultdict(lambda: float("inf")) |
两种记忆化搜索还需要再详细思考一下
198.打家劫舍
https://leetcode-cn.com/problems/house-robber/
题解
- 失业程序员转职做小偷(?)
解法
- 动态规划
1 | if len(nums) == 0: return 0 |
空间优化(奇怪的是时间减半但内存消耗增加了)
1 | prev = 0 |